# LangChain的函数，工具和代理(一)：OpenAI的函数调用

import json
import openai

openai.api_key = "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"


# 查询天气的模拟函数示例
# 1、在生产中，这可能是您的后端 API 或外部 API
def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    weather_info = {
        "location": location,  # 城市
        "temperature": "72",  # 温度
        "unit": unit,  # 温度单位
        "forecast": ["sunny", "windy"],  # 天气情况
    }
    return json.dumps(weather_info)


# 2、函数描述对象
functions = [
    {
        # 外部函数名称如get_current_weather
        "name": "get_current_weather",
        # 外部函数功能的描述,决定了LLM是否回调用此函数
        "description": "Get the current weather in a given location",
        # 外部函数的参数集
        "parameters": {
            # 外部函数的参数集的类型
            "type": "object",
            # 外部函数的具体参数集
            "properties": {
                # 具体的外部函数的参数
                "location": {
                    # 外部函数的参数的类型
                    "type": "string",
                    # 外部函数的参数的描述
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                # 具体的外部函数的参数
                "unit": {
                    # 外部函数的参数的类型
                    "type": "string",
                    # 外部函数的参数的枚举值
                    "enum": ["celsius", "fahrenheit"]},
            },
            # 必填的参数
            "required": ["location"],
        },
    }
]

# 3、向ChatGPT询问一个关于天气的问题
messages = [
    {
        "role": "user",
        "content": "上海的天气怎么样？"
    }
]
response1 = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-1106",
    messages=messages,
    functions=functions
)
# 会返回function_call信息（包含调用函数的入参+函数名）
# print(response)


# 4、从ChatGPT的返回结果中获取参数来实际调用外部函数get_current_weather:
args = json.loads(response1["choices"][0]["message"]["function_call"]["arguments"])
result = get_current_weather(args)
# print(result)


# 5、测试一下ChatGPT能否准确识别何时该调用外部函数
messages = [
    {
        "role": "user",
        "content": "hi!",
    }
]
response2 = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-1106",
    messages=messages,
    functions=functions,
    # （让模型根据上下文来确定是否调用外部函数）设置OPAI API的默认参数 - openai的API函数ChatCompletion.create中存在一个function_call的参数，该参数的默认值为“auto”即让模型自己来选择是否需要调用外部函数：
    function_call="auto",
    # 将function_cal设置为"none"时(即禁止chatGPT调用外部函数)，chatGPT的返回结果中也不会出现“function_call”的内容。
    # function_call="none",
    # 强制调用外部函数
    # function_call={"name": "get_current_weather"},
)
# print(response2)


# 6、外部函数的调用结果的应用
# 上面我们让chatgpt来判断是否应该调用外部函数，并且让chatgpt返回了调用外部函数的参数，接下来我们要做的是用chatgpt提供的参数去实际调用外部函数，并将外部函数的返回结果再喂给chatgpt，这样做的目的是让chatgpt来汇总所有的信息并产生最终对用户友好的返回信息。
# 整合chatgpt的返回结果
messages.append(response1["choices"][0]["message"])
# 从chatgpt的返回结果中获取外部函数的调用参数
args = json.loads(response1["choices"][0]["message"]['function_call']['arguments'])
# 调用外部函数
observation = get_current_weather(args)

messages.append(
    {
        "role": "function",  # 告诉语言模型这是调用函数的响应
        "name": "get_current_weather",
        "content": observation,  # 外部函数的返回结果
    }
)

response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo-0613",
    messages=messages,
)
# 这里我们看到ChatGPT最终返回了一个非常友好的回复,该回复是在外部函数调用结果的基础上经过整理后得到的。（但是Token使用数明显增加）
print(response)
